//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Publisher.Wcf
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using Microsoft.Research.eResearch.Blackboard.Common;
    using Microsoft.Research.eResearch.Common.Eventing;
    using Microsoft.Research.eResearch.Publisher.Interfaces;
    using System.Configuration;

    /// <summary>
    /// Manages the hosting of a <see cref="WcfPublisherSubscriptionService"/> service.
    /// </summary>
    public class WcfPublisherSubscriptionServiceHost : IPublisherSubscriptionReceiver
    {
        /// <summary>
        /// Starting TCP port number used for publisher.
        /// If port 43101 is not available, use the config file to provide port.
        /// </summary>
        private int BaseTcpPort = 43101;

        /// <summary>
        /// Number of ports that the publisher can use.
        /// If not defined in the config file, portRange 100 will be used as default.
        /// </summary>
        private int PortRange = 100;

        /// <summary>
        /// Address constructed for this publisher.
        /// </summary>
        private Uri baseTCPAddress;

        /// <summary>
        /// Instance of the service host.
        /// </summary>
        private ServiceHost serviceHost;

        /// <summary>
        /// Indicates whether the port is identified.  It is set to true after the first call.
        /// </summary>
        private bool portIdentified;

        /// <summary>
        /// Initializes a new instance of the <see cref="WcfPublisherSubscriptionServiceHost"/> class.
        /// </summary>
        public WcfPublisherSubscriptionServiceHost()
        {
            string configuredPortRange = ConfigurationManager.AppSettings["PortRange"];
            string configuredBaseTcpPort = ConfigurationManager.AppSettings["BaseTcpPort"];

            if (!string.IsNullOrEmpty(configuredPortRange))
                PortRange = Int32.Parse(configuredPortRange);

            if (!string.IsNullOrEmpty(configuredBaseTcpPort))
                BaseTcpPort = Int32.Parse(configuredBaseTcpPort);                      

            this.baseTCPAddress = new Uri(string.Format(CultureInfo.CurrentUICulture, "net.tcp://{0}:{1}/Blackboard/WcfPublisherSubscriptionService/", Dns.GetHostEntry(Dns.GetHostName()).HostName, BaseTcpPort));
        }

        /// <summary>
        /// Raised when a new subscription profile is received.
        /// </summary>
        public event EventHandler<PublisherSubscriptionChangedEventArgs> PublisherSubscriptionChanged;

        /// <summary>
        /// Begins listening for new subscription profile messages.
        /// </summary>
        /// <returns>The URL of the service that is listening.</returns>
        public Uri Start()
        {
            Stop();
            if (!this.portIdentified)
            {
                this.IdentifyPort();
            }

            serviceHost = this.CreateServiceHost();
            serviceHost.Open();
            ServiceEndpoint endpoint = serviceHost.Description.Endpoints
                .Where(item => typeof(IWcfPublisherSubscriptionService).IsAssignableFrom(item.Contract.ContractType))
                .FirstOrDefault();
            if (endpoint == null)
            {
                throw new InvalidOperationException("No endpoints are exposed with the correct contract type");
            }

            return endpoint.Address.Uri;
        }

        /// <summary>
        /// Stops listening for new profile notifications.
        /// </summary>
        public void Stop()
        {
            if (serviceHost != null)
            {
                serviceHost.Close();
            }
        }

        /// <summary>
        /// Raises the PublisherSubscriptionChanged event.
        /// </summary>
        /// <param name="profile">The subscription profile.</param>
        protected virtual void OnPublisherSubscriptionChanged(SubscriptionProfile profile)
        {
            PublisherSubscriptionChanged.Fire(this, new PublisherSubscriptionChangedEventArgs(profile));
        }

        /// <summary>
        /// Converts the base uri to have the new port.
        /// </summary>
        /// <param name="baseUri">Instance of the base URI.</param>
        /// <param name="newPort">Port to be updated in the base URI.</param>
        /// <returns>Instance of the modified URI.</returns>
        private static Uri ChangeUriPort(Uri baseUri, int newPort)
        {
            UriBuilder uriBuilder = new UriBuilder(baseUri);
            uriBuilder.Port = uriBuilder.Port + newPort;
            return uriBuilder.Uri;
        }

        /// <summary>
        /// Creates a service host instance.
        /// </summary>
        /// <returns>Instance of the service host.</returns>
        private ServiceHost CreateServiceHost()
        {
            ServiceHost host = new ServiceHost(typeof(WcfPublisherSubscriptionService));
            host.Description.Endpoints[0].Address = new EndpointAddress(this.baseTCPAddress.ToString());
            host.Extensions.Add(new PublisherSubscriptionReceiverExtension(OnPublisherSubscriptionChanged));
            return host;
        }

        /// <summary>
        /// Identifies the port to be used for this publisher.
        /// </summary>
        private void IdentifyPort()
        {
            Random random = new Random(DateTime.Now.Millisecond);

            for (int i = 0; i < PortRange; i++)
            {
                try
                {
                    int choice = random.Next(PortRange);
                    this.baseTCPAddress = ChangeUriPort(this.baseTCPAddress, choice);

                    ServiceHost host = this.CreateServiceHost();
                    host.Open();
                    host.Close();
                    this.portIdentified = true;
                    break;
                }
                catch (Exception)
                {
                }
            }
        }
    }
}
